package netconf

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"github.com/google/uuid"
)

const (
	editConfigXml = `<edit-conf>
<target><%s/></target>
<default-operation>merge</default-operation>
<error-option>rollback-on-error</error-option>
<conf>%s</conf>
</edit-conf>`
)

const (
	// msgSeperator is used to separate sent messages via NETCONF
	MsgSeparator     = "]]>]]>"
	MsgSeparator_v11 = "\n##\n"
)

// DefaultCapabilities sets the default capabilities of the client library
var DefaultCapabilities = []string{
	"urn:ietf:params:netconf:base:1.0",
	"urn:ietf:params:netconf:base:1.1",
	"urn:ietf:params:netconf:capability:writable-running:1.0",
	"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&amp;revision=2010-10-04",
}

// HelloMessage is used when bringing up a NETCONF session
type HelloMessage struct {
	XMLName      xml.Name `xml:"urn:ietf:params:xml:ns:netconf:base:1.0 hello"`
	Capabilities []string `xml:"capabilities>capability"`
	SessionID    int      `xml:"session-id,omitempty"`
}

// RPCMessage represents an RPC Message to be sent.
type RPCMessage struct {
	MessageID  string
	Operations []RPCOperation
}

// NewRPCMessage generates a new RPC Message structure with the provided methods
func NewRPCMessage(operations []RPCOperation) *RPCMessage {
	return &RPCMessage{
		MessageID:  uuid.NewString(),
		Operations: operations,
	}
}

// MarshalXML marshals the NETCONF XML data
func (m *RPCMessage) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	var buf bytes.Buffer
	for _, operation := range m.Operations {
		buf.WriteString(operation.Marshal())
	}

	data := struct {
		MessageID string `xml:"message-id,attr"`
		Xmlns     string `xml:"xmlns,attr"`
		Methods   []byte `xml:",innerxml"`
	}{
		m.MessageID,
		"urn:ietf:params:xml:ns:netconf:base:1.0",
		buf.Bytes(),
	}

	// Wrap the raw XML (data) into <rpc>...</rpc> tags
	start.Name.Local = "rpc"
	return e.EncodeElement(data, start)
}

// RPCReply netconf响应
type RPCReply struct {
	XMLName xml.Name   `xml:"rpc-reply"`
	Errors  []RPCError `xml:"rpc-error,omitempty"`
	Data    string     `xml:",innerxml"`
	Ok      bool       `xml:",omitempty"`
}

func newRPCReply(rawXML []byte, ErrOnWarning bool, messageID string) (*RPCReply, error) {
	reply := &RPCReply{}
	if err := xml.Unmarshal(rawXML, reply); err != nil {
		return nil, err
	}

	if reply.Errors != nil {
		for _, rpcErr := range reply.Errors {
			if rpcErr.Severity == "error" || ErrOnWarning {
				return reply, &rpcErr
			}
		}
	}

	return reply, nil
}

// RPCError defines an error reply to a RPC request
type RPCError struct {
	Type     string `xml:"error-type"`
	Tag      string `xml:"error-tag"`
	Severity string `xml:"error-severity"`
	Path     string `xml:"error-path"`
	Message  string `xml:"error-message"`
	Info     string `xml:",innerxml"`
}

func (re *RPCError) Error() string {
	return fmt.Sprintf("netconf rpc [%s] '%s'", re.Severity, re.Message)
}

type RPCOperation interface {
	Marshal() string
}

type RawOperation string

func (r RawOperation) Marshal() string {
	return string(r)
}

func LockOperation(target string) RawOperation {
	return RawOperation(fmt.Sprintf("<lock><target><%s/></target></lock>", target))
}

func UnlockOperation(target string) RawOperation {
	return RawOperation(fmt.Sprintf("<unlock><target><%s/></target></unlock>", target))
}

func GetConfigOperation(source string) RawOperation {
	return RawOperation(fmt.Sprintf("<get-conf><source><%s/></source></get-conf>", source))
}

func GetOperation(filterType string, dataXml string) RawOperation {
	return RawOperation(fmt.Sprintf("<get><filter type=\"%s\">%s</filter></get>", filterType, dataXml))
}

func EditConfigOperation(database string, dataXml string) RawOperation {
	return RawOperation(fmt.Sprintf(editConfigXml, database, dataXml))
}
